package edu.cmu.arch.pipesandfilters;

/**
 * This class takes the input from one pipe and can be used to send data
 * to two output pipes.  Each output pipe must implement its own runnable method
 * independent of one another.  See runFilter1 and runFilter2 for more details.
 * This class is abstract, extend it and implement runFilter1 and runFilter2.
 * 
 * @author Kyle Benedict (jbenedic@andrew.cmu.edu)
 */
public abstract class DualOutputFilterFramework extends FrameFilterFramework {
	
	private class OutputFilter extends FrameFilterFramework {
		@Override
		public void run() {
			runFilter2();
			runFilter2Finished = true;
		
		}
	} 
	private OutputFilter outputFilter2 = null;
	protected boolean runFilter1Finished = false;
	protected boolean runFilter2Finished = false;
	
	/**
	 * The default constructor.
	 */
	public DualOutputFilterFramework() {
		outputFilter2 = new  OutputFilter();
		
	}
	
	/**
	 * Getter for the first filter, equivalent to this object itself.
	 * @return FilterFramework
	 */
	public final FilterFramework getOutputFilter1() {
		return this;
	}
	
	/**
	 * Getter for the second filter.  
	 * @return FilterFramework
	 */
	public final FilterFramework getOutputFilter2() {
		return outputFilter2;
	}
	
	/**
	 * Runnable method that can be used to operate on the first output 
	 * filter.  Note that the order the two runnable methods (runFilter1() and
	 * runFilter2()) is indeterminate.  If you rely on data being created in 
	 * the runFilter2 method, you must block and wait for it to complete to 
	 * ensure your data is available.  To do this, call 
	 * blockUntilRunFilter2IsFinished().
	 */
	protected abstract void runFilter1();
	
	/**
	 * Runnable method that can be used to operate on the second output 
	 * filter.  Note that the order the two runnable methods (runFilter1() and
	 * runFilter2()) is indeterminate.  If you rely on data being created in 
	 * the runFilter1 method, you must block and wait for it to complete to 
	 * ensure your data is available.  To do this, call 
	 * blockUntilRunFilter1IsFinished().
	 */
	protected abstract void runFilter2();
	
	/**
	 * Blocks until the runFilter1 method has completed.
	 */
	public final void blockUntilRunFilter1IsFinished() {
		while (!runFilter1Finished) {
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {			
				System.out.println(this.getName() + "::blockUntilRunFilter1IsFinished() InterruptedException occured" + e.getMessage());
			}
		}
	}
	
	/**
	 * Blocks until the runFilter2 method has completed.
	 */
	public final void blockUntilRunFilter2IsFinished() {
		while (!runFilter2Finished) {
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {		
				System.out.println(this.getName() + "::blockUntilRunFilter2IsFinished() InterruptedException occured" + e.getMessage());
			}
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cmu.arch.pipesandfilters.FilterFramework#run()
	 */
	@Override
	public final void run() {
		runFilter1();
		runFilter1Finished = true;
	}
	
	/**
	 * Allows data to be written to the second filter.  
	 * @param datum
	 */
	protected final void WriteFilterOutputPort2(byte datum) {
		getOutputFilter2().WriteFilterOutputPort(datum);
	}
	
	/**
	 * Allows data to be written to the first filter.  
	 * @param datum
	 */
	protected final void WriteFilterOutputPort1(byte datum) {
		WriteFilterOutputPort(datum);
	}
	
	/**
	 * Closes the second filter's ports.  
	 */
	void ClosePorts2() {
		getOutputFilter2().ClosePorts();
	}
	
	/**
	 * Closes the first filter's ports.  
	 */
	void ClosePorts1() {
		ClosePorts();
	}
	
	/**
	 * Starts the thread processing for both output filters.
	 */
	@Override	
	public synchronized void start() {
		if (Thread.State.NEW.equals(outputFilter2.getState())) {
			// If the caller hasn't already explicity invoked this
			// thread's start method (e.g., via getOutputFilter2().start())
			// then go ahead and invoke it now.
			outputFilter2.start();
		}
		super.start();
	}

}
